/*****************************************************************************
 * Free42 -- an HP-42S calculator simulator
 * Copyright (C) 2004-2011  Thomas Okken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/licenses/.
 *****************************************************************************/

#include <stdlib.h>

#include "core_commands2.h"
#include "core_commands7.h"
#include "core_display.h"
#include "core_helpers.h"
#include "core_main.h"
#include "core_variables.h"
#include "shell.h"

//////////////////////////////////////////////////////////////////////////
/////     Accelerometer, Location Services, and Compass support      /////
///// iPhone only, for now. In order to compile this, the shell must /////
/////   provide shell_get_acceleration() etc., and those are only    /////
/////             implemented in the iPhone shell so far.            /////
//////////////////////////////////////////////////////////////////////////

#if defined(ANDROID) || defined(IPHONE)
int docmd_accel(arg_struct *arg) {
    if (!core_settings.enable_ext_accel)
	return ERR_NONEXISTENT;
    double x, y, z;
    int err = shell_get_acceleration(&x, &y, &z);
    if (err == 0)
	return ERR_NONEXISTENT;
    vartype *new_x = new_real(x);
    vartype *new_y = new_real(y);
    vartype *new_z = new_real(z);
    if (new_x == NULL || new_y == NULL || new_z == NULL) {
	free_vartype(new_x);
	free_vartype(new_y);
	free_vartype(new_z);
	return ERR_INSUFFICIENT_MEMORY;
    }
    free_vartype(reg_t);
    free_vartype(reg_z);
    if (flags.f.stack_lift_disable) {
	free_vartype(reg_x);
	reg_t = reg_y;
    } else {
	free_vartype(reg_y);
	reg_t = reg_x;
    }
    reg_z = new_z;
    reg_y = new_y;
    reg_x = new_x;
    if (flags.f.trace_print && flags.f.printer_exists)
	docmd_prx(NULL);
    return ERR_NONE;
}

int docmd_locat(arg_struct *arg) {
    if (!core_settings.enable_ext_locat)
	return ERR_NONEXISTENT;
    double lat, lon, lat_lon_acc, elev, elev_acc;
    int err = shell_get_location(&lat, &lon, &lat_lon_acc, &elev, &elev_acc);
    if (err == 0)
	return ERR_NONEXISTENT;
    vartype *new_x = new_real(lat);
    vartype *new_y = new_real(lon);
    vartype *new_z = new_real(elev);
    vartype *new_t = new_realmatrix(1, 2);
    if (new_x == NULL || new_y == NULL || new_z == NULL || new_t == NULL) {
	free_vartype(new_x);
	free_vartype(new_y);
	free_vartype(new_z);
	free_vartype(new_t);
	return ERR_INSUFFICIENT_MEMORY;
    }
    vartype_realmatrix *rm = (vartype_realmatrix *) new_t;
    rm->array->data[0] = lat_lon_acc;
    rm->array->data[1] = elev_acc;
    free_vartype(reg_t);
    free_vartype(reg_z);
    free_vartype(reg_y);
    free_vartype(reg_x);
    reg_t = new_t;
    reg_z = new_z;
    reg_y = new_y;
    reg_x = new_x;
    if (flags.f.trace_print && flags.f.printer_exists)
	docmd_prx(NULL);
    return ERR_NONE;
}

int docmd_heading(arg_struct *arg) {
    if (!core_settings.enable_ext_heading)
	return ERR_NONEXISTENT;
    double mag_heading, true_heading, acc, x, y, z;
    int err = shell_get_heading(&mag_heading, &true_heading, &acc, &x, &y, &z);
    if (err == 0)
	return ERR_NONEXISTENT;
    vartype *new_x = new_real(mag_heading);
    vartype *new_y = new_real(true_heading);
    vartype *new_z = new_real(acc);
    vartype *new_t = new_realmatrix(1, 3);
    if (new_x == NULL || new_y == NULL || new_z == NULL || new_t == NULL) {
	free_vartype(new_x);
	free_vartype(new_y);
	free_vartype(new_z);
	free_vartype(new_t);
	return ERR_INSUFFICIENT_MEMORY;
    }
    vartype_realmatrix *rm = (vartype_realmatrix *) new_t;
    rm->array->data[0] = x;
    rm->array->data[1] = y;
    rm->array->data[2] = z;
    free_vartype(reg_t);
    free_vartype(reg_z);
    free_vartype(reg_y);
    free_vartype(reg_x);
    reg_t = new_t;
    reg_z = new_z;
    reg_y = new_y;
    reg_x = new_x;
    if (flags.f.trace_print && flags.f.printer_exists)
	docmd_prx(NULL);
    return ERR_NONE;
}
#endif

/////////////////////////////////////////////////
///// HP-41 Time Module & CX Time emulation /////
/////////////////////////////////////////////////

static int date2comps(phloat x, int4 *yy, int4 *mm, int4 *dd) COMMANDS7_SECT;
static int date2comps(phloat x, int4 *yy, int4 *mm, int4 *dd) {
    int4 m = to_int4(floor(x));
#ifdef BCD_MATH
    int4 d = to_int4(floor((x - m) * 100));
    int4 y = to_int4(x * 1000000) % 10000;
#else
    int4 r = (int4) floor((x - m) * 100000000 + 0.5);
    r /= 100;
    int4 d = r / 10000;
    int4 y = r % 10000;
#endif

    if (mode_time_dmy) {
	int4 t = m;
	m = d;
	d = t;
    }

    if (y < 1582 || y > 4320 || m < 1 || m > 12 || d < 1 || d > 31)
	return ERR_INVALID_DATA;
    if ((m == 4 || m == 6 || m == 9 || m == 11) && d == 31)
	return ERR_INVALID_DATA;
    if (m == 2 && d > ((y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 29 : 28))
	return ERR_INVALID_DATA;
    if (y == 1582 && (m < 10 || m == 10 && d < 15)
	    || y == 4320 && (m > 9 || m == 9 && d > 10))
	return ERR_INVALID_DATA;

    *yy = y;
    *mm = m;
    *dd = d;
    return ERR_NONE;
}

static phloat comps2date(int4 y, int4 m, int4 d) COMMANDS7_SECT;
static phloat comps2date(int4 y, int4 m, int4 d) {
    if (mode_time_dmy) {
	int4 t = m;
	m = d;
	d = t;
    }
    return phloat(m * 1000000 + d * 10000 + y) / 1000000;
}

/* Gregorian Date <-> Day Number conversion functions
 * Algorithm due to Henry F. Fliegel and Thomas C. Van Flandern,
 * Communications of the ACM, Vol. 11, No. 10 (October, 1968).
 */
static int greg2jd(int4 y, int4 m, int4 d, int4 *jd) COMMANDS7_SECT;
static int greg2jd(int4 y, int4 m, int4 d, int4 *jd) {
    *jd = ( 1461 * ( y + 4800 + ( m - 14 ) / 12 ) ) / 4 +
	  ( 367 * ( m - 2 - 12 * ( ( m - 14 ) / 12 ) ) ) / 12 -
	  ( 3 * ( ( y + 4900 + ( m - 14 ) / 12 ) / 100 ) ) / 4 +
	  d - 32075;
    return ERR_NONE;
}

static int jd2greg(int4 jd, int4 *y, int4 *m, int4 *d) COMMANDS7_SECT;
static int jd2greg(int4 jd, int4 *y, int4 *m, int4 *d) {
    if (jd < 2299161 || jd > 3299160)
	return ERR_OUT_OF_RANGE;
    int4 l = jd + 68569;
    int4 n = ( 4 * l ) / 146097;
    l = l - ( 146097 * n + 3 ) / 4;
    int4 i = ( 4000 * ( l + 1 ) ) / 1461001;
    l = l - ( 1461 * i ) / 4 + 31;
    int4 j = ( 80 * l ) / 2447;
    *d = l - ( 2447 * j ) / 80;
    l = j / 11;
    *m = j + 2 - ( 12 * l );
    *y = 100 * ( n - 49 ) + i + l;
    return ERR_NONE;
}


int docmd_adate(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    if (reg_x->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_x->type != TYPE_REAL)
	return ERR_INVALID_TYPE;

    phloat x = ((vartype_real *) reg_x)->x;
    if (x < 0)
	x = -x;
    if (x >= 100)
	return ERR_INVALID_DATA;
    int m = to_int(floor(x));
    int4 dy = to_int4(floor((x - floor(x)) * 1000000));
    int d = (int) (dy / 10000);
    int c = (int) (dy / 100 % 100);
    int y = (int) (dy % 100);

    int digits;
    if (flags.f.fix_or_all && flags.f.eng_or_all)
	digits = 11;
    else {
	digits = 0;
	if (flags.f.digits_bit3)
	    digits += 8;
	if (flags.f.digits_bit2)
	    digits += 4;
	if (flags.f.digits_bit1)
	    digits += 2;
	if (flags.f.digits_bit0)
	    digits += 1;
    }

    char buf[10];
    int bufptr = 0;
    if (m < 10)
	char2buf(buf, 10, &bufptr, '0');
    bufptr += int2string(m, buf + bufptr, 10 - bufptr);
    if (digits > 0) {
	char2buf(buf, 10, &bufptr, mode_time_dmy ? '.' : '/');
	if (d < 10)
	    char2buf(buf, 10, &bufptr, '0');
	bufptr += int2string(d, buf + bufptr, 10 - bufptr);
	if (digits > 2) {
	    char2buf(buf, 10, &bufptr, mode_time_dmy ? '.' : '/');
	    if (digits > 4) {
		if (c < 10)
		    char2buf(buf, 10, &bufptr, '0');
		bufptr += int2string(c, buf + bufptr, 10 - bufptr);
	    }
	    if (y < 10)
		char2buf(buf, 10, &bufptr, '0');
	    bufptr += int2string(y, buf + bufptr, 10 - bufptr);
	}
    }

    append_alpha_string(buf, bufptr, 0);
    return ERR_NONE;
}

int docmd_atime(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    if (reg_x->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_x->type != TYPE_REAL)
	return ERR_INVALID_TYPE;

    phloat x = ((vartype_real *) reg_x)->x;
    bool neg = x < 0;
    if (neg)
	x = -x;
    if (x >= 100)
	return ERR_INVALID_DATA;
    int h = to_int(floor(x));
    if (h == 0)
	neg = false;
    int4 ms = to_int4(floor((x - floor(x)) * 1000000));
    int m = (int) (ms / 10000);
    int s = (int) (ms / 100 % 100);
    int cs = (int) (ms % 100);
    bool am = false;
    bool pm = false;

    if (mode_time_clk24) {
	if (neg && h >= 1 && h <= 11)
	    h += 12;
    } else if (h < 24) {
	if (!neg && h < 12)
	    am = true;
	else
	    pm = true;
	if (h == 0)
	    h = 12;
	else if (h > 12)
	    h -= 12;
    }

    int digits;
    if (flags.f.fix_or_all && flags.f.eng_or_all)
	digits = 11;
    else {
	digits = 0;
	if (flags.f.digits_bit3)
	    digits += 8;
	if (flags.f.digits_bit2)
	    digits += 4;
	if (flags.f.digits_bit1)
	    digits += 2;
	if (flags.f.digits_bit0)
	    digits += 1;
    }

    char buf[14];
    int bufptr = 0;
    if (h < 10)
	char2buf(buf, 14, &bufptr, mode_time_clk24 ? '0' : ' ');
    bufptr += int2string(h, buf + bufptr, 14 - bufptr);
    if (digits > 0) {
	char2buf(buf, 14, &bufptr, ':');
	if (m < 10)
	    char2buf(buf, 14, &bufptr, '0');
	bufptr += int2string(m, buf + bufptr, 14 - bufptr);
	if (digits > 2) {
	    char2buf(buf, 14, &bufptr, ':');
	    if (s < 10)
		char2buf(buf, 14, &bufptr, '0');
	    bufptr += int2string(s, buf + bufptr, 14 - bufptr);
	    if (digits > 4) {
		char2buf(buf, 14, &bufptr, '.');
		if (cs < 10)
		    char2buf(buf, 14, &bufptr, '0');
		bufptr += int2string(cs, buf + bufptr, 14 - bufptr);
	    }
	}
    }
    if (am)
	string2buf(buf, 14, &bufptr, " AM", 3);
    else if (pm)
	string2buf(buf, 14, &bufptr, " PM", 3);
    append_alpha_string(buf, bufptr, 0);

    return ERR_NONE;
}

int docmd_atime24(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    bool saved_clk24 = mode_time_clk24;
    mode_time_clk24 = true;
    int res = docmd_atime(arg);
    mode_time_clk24 = saved_clk24;
    return res;
}

int docmd_clk12(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    mode_time_clk24 = false;
    return ERR_NONE;
}

int docmd_clk24(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    mode_time_clk24 = true;
    return ERR_NONE;
}

static char weekdaynames[] = "SUNMONTUEWEDTHUFRISAT";

int docmd_date(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    uint4 date;
    int weekday;
    shell_get_time_date(NULL, &date, &weekday);
    int y = date / 10000;
    int m = date / 100 % 100;
    int d = date % 100;
    if (mode_time_dmy)
	date = y + m * 10000L + d * 1000000;
    else
	date = y + m * 1000000 + d * 10000L;
    vartype *new_x = new_real((int4) date);
    if (new_x == NULL)
	return ERR_INSUFFICIENT_MEMORY;
    ((vartype_real *) new_x)->x /= 1000000;
    recall_result(new_x);
    if (!program_running()) {
	/* Note: I'm not completely faithful to the HP-41 here. It formats the
	 * date as "14.03.2010 SUN" in DMY mode, and as "03/14/2010:SU" in MDY
	 * mode. I mimic the former, but the latter I changed to
	 * "03/14/2010 SUN"; the MDY display format used on the HP-41 is the
	 * way it is because that was all they could fit in its 12-character
	 * display. (Note that the periods in the DMY format and the colon in
	 * the MDY format don't take up a character position on the HP-41.)
	 */
	char buf[22];
	int bufptr = 0;
	int n = mode_time_dmy ? d : m;
	if (n < 10)
	    char2buf(buf, 22, &bufptr, '0');
	bufptr += int2string(n, buf + bufptr, 22 - bufptr);
	char2buf(buf, 22, &bufptr, mode_time_dmy ? '.' : '/');
	n = mode_time_dmy ? m : d;
	if (n < 10)
	    char2buf(buf, 22, &bufptr, '0');
	bufptr += int2string(n, buf + bufptr, 22 - bufptr);
	char2buf(buf, 22, &bufptr, mode_time_dmy ? '.' : '/');
	bufptr += int2string(y, buf + bufptr, 22 - bufptr);
	char2buf(buf, 22, &bufptr, ' ');
	string2buf(buf, 22, &bufptr, weekdaynames + weekday * 3, 3);
	clear_row(0);
	draw_string(0, 0, buf, bufptr);
	flush_display();
	flags.f.message = 1;
	flags.f.two_line_message = 0;
    }
    /* TODO: Trace-mode printing. What should I print, the contents of X,
     * or, when not in a running program, the nicely formatted date?
     */
    return ERR_NONE;
}

int docmd_date_plus(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    // TODO: Accept real matrices as well?
    if (reg_x->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_x->type != TYPE_REAL)
	return ERR_INVALID_TYPE;
    if (reg_y->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_y->type != TYPE_REAL)
	return ERR_INVALID_TYPE;

    phloat date = ((vartype_real *) reg_y)->x;
    if (date < 0 || date > 100)
	return ERR_INVALID_DATA;
    phloat days = ((vartype_real *) reg_x)->x;
    if (days < -1000000 || days > 1000000)
	return ERR_OUT_OF_RANGE;

    int4 y, m, d, jd;
    int err = date2comps(date, &y, &m, &d);
    if (err != ERR_NONE)
	return err;
    err = greg2jd(y, m, d, &jd);
    if (err != ERR_NONE)
	return err;
    jd += to_int4(floor(days));
    err = jd2greg(jd, &y, &m, &d);
    if (err != ERR_NONE)
	return err;
    date = comps2date(y, m, d);

    vartype *new_x = new_real(date);
    if (new_x == NULL)
	return ERR_INSUFFICIENT_MEMORY;
    binary_result(new_x);
    return ERR_NONE;
}

int docmd_ddays(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    // TODO: Accept real matrices as well?
    if (reg_x->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_x->type != TYPE_REAL)
	return ERR_INVALID_TYPE;
    if (reg_y->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_y->type != TYPE_REAL)
	return ERR_INVALID_TYPE;

    phloat date1 = ((vartype_real *) reg_y)->x;
    if (date1 < 0 || date1 > 100)
	return ERR_INVALID_DATA;
    phloat date2 = ((vartype_real *) reg_x)->x;
    if (date2 < 0 || date2 > 100)
	return ERR_INVALID_DATA;
    int4 y, m, d, jd1, jd2;
    int err = date2comps(date1, &y, &m, &d);
    if (err != ERR_NONE)
	return err;
    err = greg2jd(y, m, d, &jd1);
    if (err != ERR_NONE)
	return err;
    err = date2comps(date2, &y, &m, &d);
    if (err != ERR_NONE)
	return err;
    err = greg2jd(y, m, d, &jd2);
    if (err != ERR_NONE)
	return err;

    vartype *new_x = new_real(jd2 - jd1);
    if (new_x == NULL)
	return ERR_INSUFFICIENT_MEMORY;
    binary_result(new_x);
    return ERR_NONE;
}

int docmd_dmy(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    mode_time_dmy = true;
    return ERR_NONE;
}

int docmd_dow(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    // TODO: Accept real matrices as well?
    if (reg_x->type == TYPE_STRING)
	return ERR_ALPHA_DATA_IS_INVALID;
    if (reg_x->type != TYPE_REAL)
	return ERR_INVALID_TYPE;

    phloat x = ((vartype_real *) reg_x)->x;
    if (x < 0 || x > 100)
	return ERR_INVALID_DATA;

    int4 y, m, d, jd;
    int err = date2comps(x, &y, &m, &d);
    if (err != ERR_NONE)
	return err;
    err = greg2jd(y, m, d, &jd);
    if (err != ERR_NONE)
	return err;
    jd = (jd + 1) % 7;

    vartype *new_x = new_real(jd);
    if (new_x == NULL)
	return ERR_INSUFFICIENT_MEMORY;
    unary_result(new_x);

    if (!program_running()) {
	clear_row(0);
	draw_string(0, 0, weekdaynames + jd * 3, 3);
	flush_display();
	flags.f.message = 1;
	flags.f.two_line_message = 0;
    }

    return ERR_NONE;
}

int docmd_mdy(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    mode_time_dmy = false;
    return ERR_NONE;
}

int docmd_time(arg_struct *arg) {
    if (!core_settings.enable_ext_time)
	return ERR_NONEXISTENT;
    uint4 time;
    shell_get_time_date(&time, NULL, NULL);
    vartype *new_x = new_real((int4) time);
    if (new_x == NULL)
	return ERR_INSUFFICIENT_MEMORY;
    ((vartype_real *) new_x)->x /= 1000000;
    recall_result(new_x);
    if (!program_running()) {
	int h = time / 1000000;
	bool am;
	if (!mode_time_clk24) {
	    am = h < 12;
	    h = h % 12;
	    if (h == 0)
		h = 12;
	}
	int m = time / 10000 % 100;
	int s = time / 100 % 100;
	char buf[22];
	int bufptr = 0;
	if (h < 10)
	    char2buf(buf, 22, &bufptr, ' ');
	bufptr += int2string(h, buf + bufptr, 22 - bufptr);
	char2buf(buf, 22, &bufptr, ':');
	if (m < 10)
	    char2buf(buf, 22, &bufptr, '0');
	bufptr += int2string(m, buf + bufptr, 22 - bufptr);
	char2buf(buf, 22, &bufptr, ':');
	if (s < 10)
	    char2buf(buf, 22, &bufptr, '0');
	bufptr += int2string(s, buf + bufptr, 22 - bufptr);
	if (!mode_time_clk24) {
	    char2buf(buf, 22, &bufptr, ' ');
	    char2buf(buf, 22, &bufptr, am ? 'A' : 'P');
	    char2buf(buf, 22, &bufptr, 'M');
	}
	clear_row(0);
	draw_string(0, 0, buf, bufptr);
	flush_display();
	flags.f.message = 1;
	flags.f.two_line_message = 0;
    }
    /* TODO: Trace-mode printing. What should I print, the contents of X,
     * or, when not in a running program, the nicely formatted time?
     */
    return ERR_NONE;
}
